package com.zjl.redis.第16章_分布式锁;

import cn.hutool.core.util.IdUtil;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import org.junit.Test;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.JsePlatform;

import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 运行顺序
 *      模拟1
 *      模拟分布式加锁2和3
 *      模拟分布式加锁2和3
 *      模拟4
 *
 *
 */
public class B2_手写分布式锁之setnx模拟_lua {

    public static RedisCommands<String, String> getRedisCommands() {
        //构建 配置
        RedisURI redis2 = RedisURI.builder()
                .redis("82.157.71.243", 6379)//设置 ip 端口
                .withHost("82.157.71.243")//设置 ip
                .withPort(6379)//设置  端口
                .withAuthentication("default", "er485g96er8")//设置 用户名密码
                .withPassword("er485g96er8")//设置 密码
                .build();

        //连接 reids
        RedisClient redisClient = RedisClient.create(redis2);
        StatefulRedisConnection<String, String> connect = redisClient.connect();
        //创建 操作类
        RedisCommands<String, String> sync = connect.sync();//同步的连接
        return sync;
    }

    private static String AMT = "amt";
    private static String LOCK = "LOCK";
    private static int num = 100;
    private static int fbsNum = 2;

    public static void main(String[] args) {

        String value = IdUtil.simpleUUID();
        RedisCommands<String, String> sync = getRedisCommands();
        String lua = """
                       if redis.call('get',KEYS[1]) == KEYS[2] then
                           return redis.call('del',KEYS[1])
                       else
                           return 0
                       end
                            """;
        Object eval = sync.eval(lua,
                ScriptOutputType.BOOLEAN,
                LOCK, "aaa",value);
        System.out.println(eval);
    }
    @Test
    public void 模拟1() {
        RedisCommands<String, String> sync = getRedisCommands();

        System.out.println(sync.get(AMT));
        sync.set(AMT, num + "");
//        sync.set(LOCK, "xxx");
    }

    @Test
    public void 模拟4() {
        RedisCommands<String, String> sync = getRedisCommands();
        System.out.println(sync.del(LOCK));
    }

    @Test //运行 fbsNum （两）次  一般不让运行两次 可以  idea中
    // 鼠标右键-> Modify Run Configuration -> Modify options -> Allow multiple instances  打上对号即可
    public void 模拟分布式加锁2和3() throws InterruptedException {

        //模拟每个java线程
        ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000));
        RedisCommands<String, String> lock = getRedisCommands();
        RedisCommands<String, String> redis = getRedisCommands();

        SetArgs setParams = new SetArgs();
        setParams.nx();
//        setParams.nx().ex(50);//过期时间   目的是防止 获取锁的java线程突然挂了，导致别的java程序一直获取不到
        AtomicInteger atomicInteger = new AtomicInteger(0);//目的是 线程啥时候停止
        for (int i = 0; i < num / fbsNum; i++) {
            threadPool.execute(() -> {
                String value = IdUtil.simpleUUID();
                try {

                    while (lock.set(LOCK, value, setParams) == null){//获取锁
                        TimeUnit.MICROSECONDS.sleep(50);
                    }

                    atomicInteger.incrementAndGet();

                    //模拟  分布式下的数据更新的   分布式安全
                    BigDecimal bigDecimal = new BigDecimal(redis.get(AMT));
                    BigDecimal subtract = bigDecimal.subtract(BigDecimal.valueOf(1));
                    redis.set(AMT, subtract.toString());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {

                    /**
                     * Lua是一种轻量小巧的脚本语言，用标准C语言编写并以源代码形式开放，
                     * 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
                     *
                     * 设计目的
                     *      其设计目的是为了嵌入应用程序中，从而为应用程序提供灵活的扩展和定制功能。
                     * Lua特性
                     *      ●轻量级它用标准C语言编写并以源代码形式开放.编译后仅仅一百余K,可以很方便的嵌入别的程序里.
                     *      ●可扩展Lua提供了非常易于使用的扩展接口和机制: 由宿主语言(通常是C或C++)提供这些功能，
                     *              Lua可以使用它们，就像是本来就内置的功能一样。
                     * if                     then
                     *
                     * elseif             then
                     *
                     * else
                     *
                     * end
                     *
                     */

//                    if(value.equals(lock.get(LOCK))){//没有原子操作
//                        lock.del(LOCK);//释放锁
//                    }

                    //lua 脚本原子操作
                    String lua = """
                       if redis.call('get',KEYS[1]) == KEYS[2] then
                           return redis.call('del',KEYS[1])
                       else
                           return 0
                       end
                            """;
                    boolean eval = lock.eval(
                            lua,//脚本
                            ScriptOutputType.BOOLEAN,//返回值类型
                            LOCK, "aaa",value);


                }
            });
        }
        while (atomicInteger.get() < (num / fbsNum)) {
            System.out.println(atomicInteger.get());
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println("停止了**************");
        threadPool.shutdown();

    }


}
